/*
日志落地模块的实现
  1.抽象落地基类
  2.派生子类（根据不同的落地方向进行派生）
  3.使用工厂模式进行创建与表示的分离
 */
#ifndef __M_SINK_H__
#define __M_SINK_H__
#include "util.hpp"
#include <fstream>
#include <assert.h>
#include <fstream>
#include <sstream>
namespace xglog
{
  class LogSink
  {
  public:
    using ptr = std::shared_ptr<LogSink>;
    virtual ~LogSink() {};
    virtual void log(const char *data, size_t len) = 0;
  };
  // 落地方向：
  // 标准输出
  class StdoutSink : public LogSink
  {
  public:
    // 将日志消息写入到标准输出
    void log(const char *data, size_t len)
    {
      std::cout.write(data, len);
    }
  };
  // 指定文件
  class FileSink : public LogSink
  {
  public:
    // 构造时传入文件名，并打开文件
    FileSink(const std::string &pathname) : _pathname(pathname)
    {
      // 1.创建日志文件所在目录
      util::File::createDirctory(util::File::path(pathname));
      // 2.创建并打开文件
      _ofs.open(_pathname, std::ios::binary | std::ios::app);
      assert(_ofs.is_open());
    }
    // 将日志消息写入到文件
    void log(const char *data, size_t len)
    {
      _ofs.write(data, len);
      assert(_ofs.good());
    }

  private:
    std::string _pathname;
    std::ofstream _ofs;
  };
  // 滚动文件（以大小进行滚动）
  class RollBySizeSink : public LogSink
  {
  public:
    RollBySizeSink(const std::string &basename, size_t max_size) : _basename(basename), _max_fsize(max_size), _cur_fsize(0), _name_count(0)
    {
      std::string pathname = createNewFile(); // 根据basename创建新的路径
      // 1.创建日志文件所在的目录
      util::File::createDirctory(util::File::path(pathname));
      // 2.创建并打开日志文件
      _ofs.open(pathname, std::ios::binary | std::ios::app); // 以追加的方式打开文件
      assert(_ofs.is_open());
    }
    // 将日志文件输入到文件，写入前判断文件大小，超过了最大就要切换文件
    void log(const char *data, size_t len) // 进行大小判断，超过指定大小，创建新文件
    {
      if (_cur_fsize >= _max_fsize)
      {
        _ofs.close();
        std::string pathname = createNewFile();
        _ofs.open(pathname, std::ios::binary | std::ios::app);
        assert(_ofs.is_open());
        _cur_fsize = 0;
      }
      _ofs.write(data, len);
      assert(_ofs.good());
      _cur_fsize += len;
    }

  private:
    // 进行大小判断，如果超过指定大小，就要创建新文件
    std::string createNewFile()
    {
      // 获取系统时间，以时间来构造文件扩展名
      time_t t = util::Date::now();
      struct tm lt;
      localtime_r(&t, &lt);
      std::stringstream filename;
      filename << _basename;
      filename << lt.tm_year + 1900;
      filename << lt.tm_mon + 1;
      filename << lt.tm_mday;
      filename << lt.tm_hour;
      filename << lt.tm_min;
      filename << lt.tm_sec;
      filename << "-";
      filename << _name_count++;
      filename << ".log";
      return filename.str();
    }

  private:
    // 基础文件名 + 扩展文件名（以时间生成） = 输出文件名
    std::string _basename; //./logs/base-    ./logs/base-200232121.log
    std::ofstream _ofs;
    size_t _max_fsize; // 记录当前文件可记录最大大小，超过之后就要切换文件
    size_t _cur_fsize; // 记录当前文件已经写入的数据大小
    size_t _name_count;
  };

  class SinkFactory
  {
  public:
    template <typename SinkType, typename... Args>
    static LogSink::ptr create(Args &&...args)
    {
      return std::make_shared<SinkType>(std::forward<Args>(args)...); // 创建对象，自动调用子类构造函数，从而实现落地
    }
  };
}
#endif